package com.maiji.cloud.utils;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.IService;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.maiji.cloud.entities.login.UploadRecord;
import com.maiji.cloud.entities.shopingmall.Goods;
import com.maiji.cloud.entities.shopingmall.ShopingCart;
import com.maiji.cloud.microservice.MicroService;
import com.maiji.cloud.response.login.UploadImageResData;
import com.maiji.cloud.response.login.UserInfoResData;
import com.maiji.cloud.response.shopingmall.GoodsResData;
import com.maiji.cloud.response.shopingmall.ShopingCartResData;
import com.maiji.cloud.response.shopingmall.ShopingOrderResData;
import com.maiji.cloud.utils.UUID_MD5;
import org.springframework.beans.BeanUtils;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class BaseService {

    /**
     * 查找一对多关系：中间表维护关联关系
     * @param parentList
     * @param parentKeyMethodName
     * @param subService
     * @param midKeyColumn
     * @param midMapValueMethodName
     * @param <A>
     * @param <M>
     * @return
     */
    public static <A, M> Map<String, List<?>> findOneMidMany (List<A> parentList, String parentKeyMethodName,
                                                              IService<M> subService, String midKeyColumn, String midMapValueMethodName) {
        List<String> parentKeys = parentList.parallelStream().map(parent -> {
            try {
                return getInvoke(parent, parentKeyMethodName, String.class);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }).collect(Collectors.toList());
        HashMap<String, List<?>> map = Maps.newHashMap();
        EntityWrapper<M> entityWrapper = new EntityWrapper<>();
        entityWrapper.in(midKeyColumn, parentKeys);
        List<M> midList = subService.selectList(entityWrapper);
        map.put("midList", midList);
        if (midList.size() == 0) map.put("subKeys", Lists.newArrayList());
        List<String> subKeys = midList.parallelStream().map(mid -> {
            try {
                return getInvoke(mid, midMapValueMethodName, String.class);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }).collect(Collectors.toList());
        map.put("subKeys", subKeys);
        return map;
    }

    /**
     * 查找一对一关系
     */
    public static <A, B, C, D> List<B> findOneToOne (List<A> parentList, String parentKeyMethodName, Class<B> parentResClass,
            String setSubMethodName, MicroService<C> subService, String subMapKey, String subMapKeyMethodName, Class<D> subResClass) {
        List<String> parentKeys = parentList.parallelStream().map(parent -> {
            try {
                return getInvoke(parent, parentKeyMethodName, String.class);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }).collect(Collectors.toList());
        HashMap<String, Object> map = Maps.newHashMap();
        map.put(subMapKey, parentKeys);
        return dealWithOneToOne(parentList, parentKeyMethodName, parentResClass, setSubMethodName, subService.selectList(map), subMapKeyMethodName, subResClass);
    }

    /**
     *  处理一对一关系：子类维护关联关系
     * @param parentList
     * @param parentResClass
     * @param setSubMethodName
     * @param subList
     * @param subResClass
     * @param subMapKeyMethodName
     * @param <A>
     * @param <B>
     * @param <C>
     * @param <D>
     * @return
     */
    public static <A, B, C, D> List<B> dealWithOneToOne(List<A> parentList, String parentMapKeyMethodName, Class<B> parentResClass,
                                                String setSubMethodName, List<C> subList, String subMapKeyMethodName, Class<D> subResClass) {
        Map<String, D> subResMap = subList.parallelStream().collect(Collectors.toMap(sub -> {
            try {
                return getInvoke(sub, subMapKeyMethodName, String.class);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }, sub -> {
            try {
                return copyProperties(sub, subResClass);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }));
        return parentSetSub(parentList, parentMapKeyMethodName, parentResClass, setSubMethodName, subResMap);
    }

    /**
     *  处理一对多关系：中间表维护关联关系
     * @param parentList
     * @param parentResClass
     * @param setSubMethodName
     * @param subList
     * @param subResClass
     * @param subMapKeyMethodName
     * @param <A>
     * @param <B>
     * @param <C>
     * @param <D>
     * @return
     */
    public static <A, B, C, D, E> List<B> dealWithOneMidMany(List<A> parentList, String parentMapKeyMethodName, Class<B> parentResClass, String setSubMethodName,
                List<E> midList, String midMapKeyMethodName, String midMapValueMethodName, List<C> subList, String subMapKeyMethodName, Class<D> subResClass) {
        List<D> subResDataList = subList.parallelStream().map(sub -> {
            try {
                return copyProperties(sub, subResClass);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }).collect(Collectors.toList());
        Map<String, List<E>> midListMap = midList.parallelStream().collect(Collectors.groupingBy(mid -> {
            try {
                return getInvoke(mid, midMapKeyMethodName, String.class);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }));
        List<B> parentResList = parentList.parallelStream().map(parent -> {
            try {
                String mapKey = getInvoke(parent, parentMapKeyMethodName, String.class);
                List<E> subMidList = midListMap.get(mapKey) != null ? midListMap.get(mapKey): Lists.newArrayList();
                String subMapKeyStr = subMidList.parallelStream().map(mid -> {
                    try {
                        return getInvoke(mid, midMapValueMethodName, String.class);
                    } catch (Exception e) {
                        e.printStackTrace();
                        return null;
                    }}).collect(Collectors.joining(","));
                List<D> subListEach = subResDataList.parallelStream().filter(sub -> {
                    try {
                        return subMapKeyStr.contains(getInvoke(sub, subMapKeyMethodName, String.class));
                    } catch (Exception e) {
                        e.printStackTrace();
                        return false;
                    }
                }).collect(Collectors.toList());
                return setInvoke(parent, parentResClass, setSubMethodName, subListEach);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }).collect(Collectors.toList());
        return parentResList;
    }

    /**
     *  处理一对多关系：子类维护关联关系
     * @param parentList
     * @param parentResClass
     * @param setSubMethodName
     * @param subList
     * @param subResClass
     * @param subMapKeyMethodName
     * @param <A>
     * @param <B>
     * @param <C>
     * @param <D>
     * @return
     */
    public static <A, B, C, D> List<B> dealWithOneToMany(List<A> parentList, String parentMapKeyMethodName,
            Class<B> parentResClass, String setSubMethodName, List<C> subList, String subMapKeyMethodName, Class<D> subResClass) {
        Map<String, List<D>> subResMap = subList.parallelStream().map(sub -> {
            try {
                return copyProperties(sub, subResClass);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }).collect(Collectors.groupingBy(sub -> {
            try {
                return getInvoke(sub, subMapKeyMethodName, String.class);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }));
        return parentSetSub(parentList, parentMapKeyMethodName, parentResClass, setSubMethodName, subResMap);
    }

    private static <T, I> T getInvoke(I nstance, String methodName, Class<T> resClazz) throws Exception {
        Class<I> clazz = (Class<I>) nstance.getClass();
        Method midMapKeyMethod = clazz.getDeclaredMethod(methodName);
        return (T) midMapKeyMethod.invoke(nstance);
    }

    private static <A, B> B setInvoke(A parent, Class<B> parentResClass, String setSubMethodName, Object sub) throws Exception {
        Method setMethod = null;
        if (sub != null) {
            Class subClazz = List.class.isAssignableFrom(sub.getClass())? List.class: sub.getClass();
            setMethod = parentResClass.getDeclaredMethod(setSubMethodName, subClazz);
        }
        if (parent.getClass() != parentResClass) {
            B parentResData = parentResClass.newInstance();
            BeanUtils.copyProperties(parent, parentResData);
            if (setMethod != null) return (B) setMethod.invoke(parentResData, sub);
            else return parentResData;
        } else {
            if (setMethod != null) return (B) setMethod.invoke(parent, sub);
            else return (B) parent;
        }
    }

    private static <X, Y> Y copyProperties (X instance, Class<Y> resClass) throws Exception {
        if (instance.getClass() != resClass) {
            Y subResData = resClass.newInstance();
            BeanUtils.copyProperties(instance, subResData);
            return subResData;
        } else return (Y) instance;
    }

    private static <A, B> List<B> parentSetSub (List<A> parentList, String parentMapKeyMethodName, Class<B> parentResClass, String setSubMethodName, Map subResMap) {
        List<B> parentResList = parentList.parallelStream().map(parent -> {
            try {
                String mapKey = getInvoke(parent, parentMapKeyMethodName, String.class);
                return setInvoke(parent, parentResClass, setSubMethodName, subResMap.get(mapKey));
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }).collect(Collectors.toList());
        return parentResList;
    }

    public static <T> void dealWithEntityWrapper (EntityWrapper<T> entityWrapper, List queryList, String column) {
        if (queryList != null && queryList.size() > 0) {
            if (queryList.get(0) != null) entityWrapper.ge(column, queryList.get(0));
            if (queryList.size() > 1 && queryList.get(1) != null) entityWrapper.le(column, queryList.get(1));
        }
    }

    public static void main (String[] args) throws Exception {
//        List<Goods> goodsList = Lists.newArrayList();
//        goodsList.add(new Goods().setUuId("123456789"));
//        goodsList.add(new Goods().setUuId("123456788"));
//        List<UploadRecord> uploadRecords = Lists.newArrayList();
//        uploadRecords.add(new UploadRecord().setBelongId("123456789"));
//        uploadRecords.add(new UploadRecord().setBelongId("123456788"));
//        List<GoodsResData> goodsResDataList = dealWithOneToOne(goodsList, "getUuId", GoodsResData.class, "setMainImage",
//                uploadRecords, "getBelongId", UploadImageResData.class);
//        System.out.println(goodsResDataList);
//        List<ShopingCart> shopingCarts = Lists.newArrayList();
//        shopingCarts.add(new ShopingCart().setGoodsId("123456789"));
//        shopingCarts.add(new ShopingCart().setGoodsId("123456788"));
//        List<ShopingCartResData> shopingCartResDataList = dealWithOneToOne(shopingCarts, "getGoodsId", ShopingCartResData.class, "setGoods",
//                goodsResDataList, "getUuId", GoodsResData.class);
//        System.out.println(shopingCartResDataList);
//        Goods goods = new Goods().setUuId(UUID_MD5.getUUID());
//        String getUuId = getInvoke(goods, "getUuId", String.class);
//        System.out.println(getUuId);

//        System.out.println(Goods.class == Goods.class);
        List<Goods> goodsList = Lists.newArrayList();
        System.out.println(List.class.isAssignableFrom(Goods.class));
        System.out.println(goodsList.getClass().isArray());
        System.out.println(ShopingOrderResData.class.getDeclaredMethod("setShopingCarts", List.class));
    }

}
